 1.Apache Kylin 实战之增量构建Cube
   
   在大多数业务场景下，Hive中的数据处于不断增长的状态
   为了支持在构建Cube时，无需重复处理历史数据，引入增量构建功能
   1).Segment
   Kylin将Cube划分为多个Segment（对应就是HBase中的一个表）
       一个Cube可能由1个或多个 Segment 组成。Segment是指定时间范围的Cube，
可以理解为Cube的分区
       Segment 是针对源数据中的某一个片段计算出来的 Cube 数据，代表一段时间
内源数据的预计算结果
       每个Segment用起始时间和结束时间来标志
	   一个 Segment 的起始时间等于它之前 Segment 的结束时间；它的结束时间等
于它后面那个Segment的起始时间
       同一个 Cube 下不同的 Segment 除了背后的源数据不同之外，其他如结构定
义、构建过程、优化方法、存储方式等都完全相同
   
   例如：以下为针对某个Cube的Segment
   Segment名称   					分区时间 HBase表名
   202010110000000-202010120000000  20201011 KYLIN_41Z8123
   202010120000000-202010130000000  20201012 KYLIN_5AB2141
   202010130000000-202010140000000  20201013 KYLIN_7C1151
   202010140000000-202010150000000  20201014 KYLIN_811680
   202010150000000-202010160000000  20201015 KYLIN_A11AD1
   2).全量构建与增量构建
   (1).全量构建
   在全量构建中：
       Cube中只存在唯一的一个Segment
	   该Segment没有分割时间的概念，即没有起始时间和结束时间
	   对于全量构建来说，每当需要更新Cube数据时，它不会区分历史数据和新加入
的数据，即在构建时会导入并处理所有的数据
   (2).增量构建
   在增量构建中:
       只会导入新 Segment 指定的时间区间内的原始数据，并只对这部分原始数据进
行预计算
   
   全量构建和增量构建的对比
   全量构建                  增量构建
   每次更新时都需要          每次只对需要更新的时间范围进行
   更新整个数据集            更新,计算量相对较小
   查询时不需要合并不同      查询时需要合并不同Segment的结果，
   Segment的结果             因此查询性能会受影响
   不需要后续的Segment合并   累计一定量的Segment之后,需要进行合并
   适合小数据量或全表        适合大数据量的Cube
   更新的Cube
   
   全量构建与增量构建的Cube查询方式对比：
       全量构建Cube
	       查询引擎只需向存储引擎访问单个Segment所对应的数据，无需进行
Segment之间的聚合
           为了加强性能，单个Segment的数据也有可能被分片存储到引擎的多个分区
上，查询引擎可能仍然需要对单个Segment不同分区的数据做进一步的聚合
	   增量构建Cube
           由于不同时间的数据分布在不同的Segment之中，查询引擎需要向存储引擎
请求读取各个Segment的数据
           增量构建的Cube上的查询会比全量构建的做更多的运行时聚合，通常来说
增量构建的Cube上的查询会比全量构建的Cube上的查询要慢一些
   
   对于小数据量的Cube，或者经常需要全表更新的Cube，使用全量构建需要更少的运
维精力，以少量的重复计算降低生产环境中的维护复杂度。
   对应大数据量的Cube，例一个包含较长历史数据的Cube，如果每天更新，那么大量
的资源是在用于重复计算，这种情况下可以考虑使用增量构建。   
   3).增量构建Cube过程
   (1).指定分割时间列
   增量构建Cube的定义必须包含一个时间维度，用来分割不同的Segment，这样的维
度称为分割时间列（Partition Date Column）。
   (2).增量构建过程
   在进行增量构建时，将增量部分的起始时间和结束时间作为增量构建请求的一部
分提交给Kylin的任务引擎
   任务引擎会根据起始时间和结束时间从Hive中抽取相应时间的数据，并对这部分
数据做预计算处理
   将预计算的结果封装成为一个新的Segment，并将相应的信息保存到元数据和存
储引擎中。一般来说，增量部分的起始时间等于Cube中最后一个Segment的结束时间   
   4).增量Cube构建
   步骤：定义数据源 => 定义model => 定义Cube => 构建Cube
   (1).定义数据源
   -- 数据结构类似，只是改为了分区表
drop table lagou_kylin.dw_sales1;
create table lagou_kylin.dw_sales1(
id string,
channelId string,
productId string,
regionId string,
amount int,
price double
)
partitioned by (dt string)
ROW FORMAT DELIMITED FIELDS TERMINATED BY ',';

-- 加载数据
load data local inpath "/root/kylin/dw_sales1001_data.txt"
into table lagou_kylin.dw_sales1
partition(dt="2020-10-01");

load data local inpath "/root/kylin/dw_sales1002_data.txt"
into table lagou_kylin.dw_sales1
partition(dt="2020-10-02");

load data local inpath "/root/kylin/dw_sales1003_data.txt"
into table lagou_kylin.dw_sales1
partition(dt="2020-10-03");

load data local inpath "/root/kylin/dw_sales1004_data.txt"
into table lagou_kylin.dw_sales1
partition(dt="2020-10-04");
   (2).定义Model
   增量构建的Cube需要指定分割时间列。例如：将日期分区字段添加到维度列中
   将日期分区字段添加到维度列中 
   (3).定义Cube
   (4).构建Cube
   确定起止时间
   (5).查看Segment
   (6).增量构建Cube
   构建 Cube 的作业
   (7).查看对应的Segment
   查看增量构建Cube对应的Segment
   (8).查询
select t1.dt,
	t2.regionname,
	sum(t1.price) as total_money,
	sum(t1.amount) as total_amount,
	max(t1.price) as total_money,
	min(t1.amount) as total_amount
from dw_sales1 t1 join dim_region t2 on 
t1.regionid = t2.regionid
group by t1.dt, t2.regionname
order by dt

select
	t1.dt,
	t2.regionid,
	t2.regionname,
	t3.productid,
	t3.productname,
	sum(t1.price) as total_money,
	sum(t1.amount) as total_amount
from
	dw_sales1 t1
inner join dim_region t2
on t1.regionid = t2.regionid
inner join dim_product t3
on t1.productid = t3.productid
group by
	t1.dt,
	t2.regionid,
	t2.regionname,
	t3.productid,
	t3.productname
order by
	t1.dt,
	t2.regionname,
	t3.productname   
   5).Segment管理
   增量构建的Cube每天都可能会有新的增量，这样的Cube中最终可能包含很多Segment,
这将导致Kylin性能受到严重影响。
       从执行引擎的角度来说，运行时的查询引擎需要聚合多个Segment的结果才能返
回正确的查询结果
       从存储引擎的角度来说，大量的Segment会带来大量的文件，给存储空间带来巨
大的压力
   需要采取措施控制 Cube 中 Segment 的数量。
   为了保持查询性能，需要：
	   需要定期地将某些Segment合并在一起
	   根据 Segment 保留策略自动地淘汰那些不会再被查询到的陈旧Segment
   6).手动触发合并Segment
   Kylin提供了一种简单的机制用于控制Cube中Segment的数量：合并Segments。在Web 
GUI中选中需要进行Segments合并的Cube。
   操作步骤：
   (1).单击Action→Merge
   (2).选中需要合并的Segment，可以同时合并多个Segment，但这些Segment必须是
连续的
   单击提交后系统会提交一个类型为“MERGE”的构建任务，它以选中的Segment中的
数据作为输入，将这些Segment的数据合并封装成为一个新的Segment。新的Segment
的起始时间为选中的最早的Segment的起始时间，它的结束时间为选中的最晚的Segment
的结束时间。
   查看合并任务
   注意事项
       在MERGE构建结束之前，所有选中用来合并的Segment仍然处于可用的状态
	   在MERGE类型的构建完成之前，系统将不允许提交这个Cube上任何类型的其他
构建任务
       当MERGE构建结束的时候，系统将选中合并的Segment替换为新的Segment,而被
替换下的Segment等待将被垃圾回收和清理，以节省系统资源
   7).删除Segment
   使用WebUI删除Cube的segment
   (1).disable Cube
   (2).点击 delete Segment，删除指定的segment
   8).自动合并
   手动维护Segment很繁琐，人工成本很高，Kylin中是可以支持自动合并Segment。
   在Cube Designer的“Refresh Settings”的页面中有：
       Auto Merge Thresholds
	   Retention Threshold
   “Refresh Settings”的页面
   两个设置项可以用来帮助管理Segment碎片。这两项设置搭配使用这两项设置可以
大大减少对Segment进行管理的麻烦。
   (1).Auto Merge Thresholds
   允许用户设置几个层级的时间阈值，层级越靠后，时间阈值就越大
   每当Cube中有新的Segment状态变为 READY 的时候，会自动触发一次系统自动合并
   合并策略
       尝试最大一级的时间阈值。例如：针对（7天、28天）层级的日志，先检查能否
将连续的若干个Segment合并成为一个超过28天的大Segment
           如果有个别的Segment的时间长度本身已经超过28天，系统会跳过Segment
		   如果满足条件的连续Segment还不能够累积超过28天，那么系统会使用下一
个层级的时间阈值重复寻找
   
   案例1 - 理解 Kylin 自动合并策略
       假设自动合并阈值设置为7天、28天
	   如果现在有A-H 8 个连续的Segment,它们的时间长度为28天(A)、7天(B)、
1天(C)、1天(D)、1天(E)、1天(F)、1天(G)、1天(H)
       此时，第9个Segment I加入，时间长度为1天
   
   自动合并策略为：
   1 、Kylin判断时候能将连续的Segment合并到28天这个阈值，由于Segment A已经
超过28天，会被排除
   2 、剩下的连续Segment，所有时间加一起 B + C + D + E + F + G + H + I (7 + 1
 + 1 + 1 + 1 + 1 + 1 + 1 = 14） < 28 天,无法满足28天阈值，开始尝试7天阈值 
   3 、跳过A（28）、B（7）均超过7天，排除 
   4 、剩下的连续Segment,所有时间加一起 C + D + E + F + G + H + I(1 + 1 
+ 1 + 1 + 1 + 1 + 1 = 7)达到7天阈值，触发合并，提交Merge任务。并构建一个
Segment X(7天)
   5 、合并后，Segment为：A（28天）、B（7天）、X（7天）
   6 、继续触发检查，A（28天）跳过，B + X（7 + 7 = 14）< 28天,不满足第一阈
值，重新使用第二阈值触发
   7 、跳过B、X，尝试终止
   
   案例2 - 配置自动合并4天的Segment
   操作步骤：
   (1).配置自动合并阈值为（4、28）
   (2).分别按照天构建分区Cube
   (3).自动触发合并Segment构建
   9).配置保留Segment
   自动合并是将多个Segment合并为一个Segment，以达到清理碎片的目的。保留
Segment则是及时清理不再使用的Segment。
   在很多场景中，只会对过去一段时间内的数据进行查询，例如：
	   对于某个只显示过去1年数据的报表
	   支撑它的Cube其实只需要保留过去一年类的Segment即可
	   由于数据在Hive中已经存在备份，则无需在Kylin中备份超过一年的历史数据
   
   可以将Retention Threshold设置为365.每当有新的Segment状态变为READY的时候,
系统会检查每一个Segment。如果它的结束时间距离最晚的一个Segment的结束时间已经
大于等于 “Retention Threshold” ，那么这个Segment将被视为无需保留。系统会自动
地从Cube中删除这个Segment。
   保留策略示意图
   10).使用JDBC连接操作Kylin
   要将数据以可视化方式展示出来，需要使用Kylin的JDBC方式连接执行SQL，获
取Kylin的执行结果
   使用Kylin的JDBC与JDBC操作MySQL一致
       jdbc url： jdbc:kylin://linux122:7070/lagou_sales_olap
	   用户名密码：ADMIN/KYLIN
   需求
   通过JDBC方式，查询按照日期、区域、产品维度统计订单总额/总数量结果
   开发步骤
	   导入驱动依赖
		<dependency>
      		<groupId>org.apache.kylin</groupId>
      		<artifactId>kylin-jdbc</artifactId>
      		<version>3.1.1</version>
    	</dependency>
   
   1 、创建Connection连接对象
   2 、构建SQL语句
   3 、创建Statement对象，并执行executeQuery
   4 、打印结果
   参考代码
package cn.lagou.other

import java.sql.{DriverManager, ResultSet}

object KylinJDBC {
  def main(args: Array[String]): Unit = {
    //1 、创建Connection连接对象
    val url = "jdbc:kylin://linux122:7070/lagou_sales_olap"
    val connection =DriverManager.getConnection(url, "ADMIN", "KYLIN")
    //2 、构建SQL语句
    val sql =
      """
        |select
        |	t1.dt,
        |	t2.regionid,
        |	t2.regionname,
        |	t3.productid,
        |	t3.productname,
        |	sum(t1.price) as total_money,
        |	sum(t1.amount) as total_amount
        |from
        |	dw_sales1 t1
        |inner join dim_region t2
        |on t1.regionid = t2.regionid
        |inner join dim_product t3
        |on t1.productid = t3.productid
        |group by
        |	t1.dt,
        |	t2.regionid,
        |	t2.regionname,
        |	t3.productid,
        |	t3.productname
        |order by
        |	t1.dt,
        |	t2.regionname,
        |	t3.productname
        |""".stripMargin
    //3 、创建Statement对象，并执行executeQuery
    val statement =connection.createStatement()

    val resultSet: ResultSet = statement.executeQuery(sql)
    //4 、打印结果
    while (resultSet.next()) {
      //取值
      val dt =resultSet.getString("dt")
      val regionname =resultSet.getString("regionname")
      val productname =resultSet.getString("productname")
      val total_money =resultSet.getString("total_money")
      val total_amount =resultSet.getString("total_amount")
      println(f"$dt   $regionname   $productname%-15s    $total_money%10s    $total_amount%10s")
    }
    connection.close()
  }

}
